/***************************************************
==================== JS INDEX ======================
****************************************************
01. PreLoader Js
02. Common Js
03. Nice Select Js
04. magnificPopup img view
05. magnificPopup video view
06. back to top
07. scroll wrapper
08. mobile menu Js
09. auto update year
10. Sidebar Js
11. Close Handlers (cartmini / body-overlay)
12. Search Js
13. portfolio-item-hover (service-item hover)
14. card-award hover (expanded image follows cursor)
15. panel pin section (at-panel-pin)
16. reveal-text
17. webgl images hover animation
18. Section to title zoom and item uper
19. anim-zoomin + gallery isotope
20. scale-img-from-to
21. text-scale-anim
22. text-scale-anim-2
23. scroll-scale-up-img
24. postbox-scroll-zoom
25. Sticky Header Js
26. scroll-brand-logo
27. move-up
28. at-about-svg-wrap
29. at-title-text
30. panel pin section (mg-portfolio-pin)
31. panel pin section (at-header-pin)
32. section-fix (pin section-title + stacking cards)
33. section-title-pin
34. data-countdown
35. at-item-anime marque
36. at-pricing-area
37. auto update year (.update-year)
38. fade-class-active
39. character Animation
40. Parallax Js
41. Archive blog sidebar_right sticky
42. ripple-image
43. Odometer counter
44. about-me-slider-active
45. slider-testimonial-thumbs
46. Carousel Ticker
47. customSwiper / slider-testimonial
48. scroll-move-up animation
49. scroll-move-up2 animation
50. zoom-in-full (badge-zoon-in)
51. scroll-section card stacking
52. change price / price plan toggle
53. scroll-rotate, scroll-move-right/left
54. Journey list vertical line
55. initPortfolioFilter
56. initProductDetails
57. initShopCart
58. text-scramble
****************************************************
****************************************************/
(function ($) {
'use strict';
var windowOn = $(window);
////////////////////////////////////////////////////
// 01. PreLoader Js
// windowOn.on('load', function () {
// setTimeout(function () {
// $('.page-loader').fadeOut('slow');
// }, 1000);
// gsap.to('.bar', 1.5, {
// delay: 0,
// height: 0,
// stagger: {
// amount: 0.5,
// },
// ease: 'power4.inOut',
// });
// });
// var logoLoading = document.querySelector('.page-loader-logo img');
// logoLoading.addEventListener('load', function () {
// gsap.to(logoLoading, { duration: 1, scale: 1.5 });
// });
////////////////////////////////////////////////////
// 02. Common Js
$('[data-background]').each(function () {
$(this).css('background-image', 'url( ' + $(this).attr('data-background') + ' )');
});
$('[data-width]').each(function () {
$(this).css('width', $(this).attr('data-width'));
});
$('[data-height]').each(function () {
$(this).css('height', $(this).attr('data-height'));
});
$('[data-bg-color]').each(function () {
$(this).css('background-color', $(this).attr('data-bg-color'));
});
$('[data-text-color]').each(function () {
$(this).css('color', $(this).attr('data-text-color'));
});
////////////////////////////////////////////////////
// 03. Nice Select Js
$('.at-select').niceSelect();
////////////////////////////////////////////////////
// 04. magnificPopup img view
$('.popup-image').magnificPopup({
type: 'image',
gallery: {
enabled: true,
},
});
////////////////////////////////////////////////////
// 05. magnificPopup video view
$('.popup-video').magnificPopup({
type: 'iframe',
});
////////////////////////////////////////////////////
// 06. back to top
function back_to_top() {
var $btn = $('#back_to_top');
var $wrapper = $('.back-to-top-wrapper');
// Scroll event (proper .on binding)
windowOn.on('scroll', function () {
if (windowOn.scrollTop() > 300) {
$wrapper.addClass('back-to-top-btn-show');
} else {
$wrapper.removeClass('back-to-top-btn-show');
}
});
// Click event (delegation-safe)
$(document).on('click', '#back_to_top', function (e) {
e.preventDefault();
$('html, body').animate({ scrollTop: 0 }, 300);
});
}
back_to_top();
////////////////////////////////////////////////////
// 07. scroll wrapper
if (typeof gsap !== 'undefined') {
let tl = gsap.timeline();
gsap.registerPlugin(ScrollTrigger, ScrollSmoother, ScrollToPlugin);
// Footer Fixed Bottom Reveal Effect (Kanso style) - Setup BEFORE ScrollSmoother
const footerFixedBottom = document.querySelector('.footer-fixed-bottom');
const footerFixedBottomInner = document.querySelector('.footer-fixed-bottom .at-footer-area');
const footerPlaceholder = document.querySelector('.footer-placeholder');
if (footerFixedBottom && footerPlaceholder) {
// Set placeholder height to match footer height
footerPlaceholder.style.height = footerFixedBottom.offsetHeight + 'px';
// Update on window resize
window.addEventListener('resize', () => {
footerPlaceholder.style.height = footerFixedBottom.offsetHeight + 'px';
ScrollTrigger.refresh();
});
}
if ($('#smooth-wrapper').length && $('#smooth-content').length) {
ScrollSmoother.create({
smooth: 1.35,
effects: true,
smoothTouch: 0.15,
ignoreMobileResize: true,
});
// Setup footer reveal animation after ScrollSmoother
const mainElement = document.querySelector('#smooth-content main');
if (footerFixedBottom && mainElement && footerPlaceholder) {
// Set initial footer scale
gsap.set(footerFixedBottomInner, { scale: 0.95 });
// Create a timeline for synchronized animations
const footerRevealTimeline = gsap.timeline({
scrollTrigger: {
trigger: footerPlaceholder,
start: 'top bottom',
end: 'bottom bottom',
scrub: 1,
invalidateOnRefresh: true,
},
});
// Animate main element pulling up
// footerRevealTimeline.to(
// mainElement,
// {
// y: () => -footerFixedBottom.offsetHeight,
// ease: 'none',
// },
// 0,
// );
// Animate footer scaling up (from 0.85 to 1.0)
footerRevealTimeline.to(
footerFixedBottomInner,
{
scale: 1,
ease: 'none',
},
0,
);
}
}
}
////////////////////////////////////////////////////
// 08. mobile menu Js
var tpMenuWrap = $('.at-mobile-menu-active > ul').clone();
var tpSideMenu = $('.at-offcanvas-menu nav');
tpSideMenu.append(tpMenuWrap);
if ($(tpSideMenu).find('.at-submenu').length != 0) {
$(tpSideMenu).find('.at-submenu').parent().append('');
}
var sideMenuList = $('.at-offcanvas-menu nav > ul > li button.at-menu-close, .at-offcanvas-menu nav > ul li.has-dropdown > a, .at-offcanvas-menu nav > ul li.has-dropdown > ul > li.menu-item-has-children > a');
$(sideMenuList).on('click', function (e) {
e.preventDefault();
if (!$(this).parent().hasClass('active')) {
$(this).parent().addClass('active');
$(this).siblings('.at-submenu').slideDown();
} else {
$(this).siblings('.at-submenu').slideUp();
$(this).parent().removeClass('active');
}
});
// px-offcanvas-2-area
const $main_wrap = $('.at-offcanvas-2-area');
const $openBtn = $('.hamburger-open-btn');
const $closeBtn = $('.hamburger-close-btn');
// Open menu
$openBtn.on('click', function () {
$main_wrap.addClass('menu-open');
$openBtn.addClass('active');
$closeBtn.addClass('active');
});
// Close menu
$closeBtn.on('click', function () {
$main_wrap.removeClass('menu-open');
$main_wrap.addClass('menu-open-temp');
setTimeout(() => {
$main_wrap.removeClass('menu-open-temp');
}, 2000);
$openBtn.removeClass('active');
$closeBtn.removeClass('active');
});
////////////////////////////////////////////////////
// 08.x Header 7 inline menu toggle
$(document).on('click', '.header-7 .at-header-menu-btn', function (e) {
e.preventDefault();
const $btn = $(this);
const $header = $btn.closest('.header-7');
const isOpen = $header.hasClass('is-menu-open');
$header.toggleClass('is-menu-open', !isOpen);
$btn.attr('aria-expanded', String(!isOpen));
});
////////////////////////////////////////////////////
// 09. auto update year
$('#year').text(new Date().getFullYear());
////////////////////////////////////////////////////
// 10. Sidebar Js
$('.at-menu-bar').on('click', function () {
$('.at-offcanvas').addClass('opened');
$('.body-overlay').addClass('apply');
});
$('.close-btn').on('click', function () {
$('.at-offcanvas').removeClass('opened');
$('.body-overlay').removeClass('apply');
});
$('.body-overlay').on('click', function () {
$('.at-offcanvas').removeClass('opened');
$('.body-overlay').removeClass('apply');
});
////////////////////////////////////////////////////
// 11. Close Handlers (cartmini / body-overlay)
$('.cartmini-close-btn, .body-overlay').on('click', function () {
$('.cartmini__area').removeClass('opened cartmini-opened');
$('.body-overlay').removeClass('apply');
});
////////////////////////////////////////////////////
// 12. Search Js
$('.at-search-click').on('click', function () {
$('.at-search-form-toggle,.at-search-body-overlay').addClass('active');
});
$('.at-search-close,.at-search-body-overlay').on('click', function () {
$('.at-search-form-toggle,.at-search-body-overlay').removeClass('active');
});
////////////////////////////////////////////////////
// 13. portfolio-item-hover (service-item hover)
document.addEventListener('DOMContentLoaded', function () {
let serviceItems = document.querySelectorAll('.service-item');
let images = document.querySelectorAll('.hover-image');
// initial state
if (images.length > 0) {
gsap.set(images, { opacity: 0, y: 50, scale: 1 });
if (images[0]) {
gsap.set(images[0], { opacity: 1, y: 0, zIndex: 2 });
}
}
serviceItems.forEach((item, index) => {
item.addEventListener('mouseenter', function () {
serviceItems.forEach((el) => el.classList.remove('active'));
this.classList.add('active');
images.forEach((img, i) => {
if (i === index) {
gsap.to(img, {
opacity: 1,
y: 0,
scale: 1,
duration: 0.8,
scrub: 1,
zIndex: 2,
});
} else {
gsap.to(img, {
opacity: 0,
y: 200,
duration: 0.8,
zIndex: 1,
scale: 0.8,
});
}
});
});
});
});
////////////////////////////////////////////////////
// 14. card-award hover (expanded image follows cursor)
if (typeof gsap !== 'undefined' && document.querySelectorAll('.card-award').length > 0) {
(function initCardAwardPreview() {
const cards = document.querySelectorAll('.card-award');
let preview = document.querySelector('.card-award-hover-preview');
if (!preview) {
preview = document.createElement('div');
preview.className = 'card-award-preview card-award-hover-preview';
preview.innerHTML = '
';
document.body.appendChild(preview);
}
const img = preview.querySelector('img');
if (!img) return;
const offsetX = 18;
const offsetY = 18;
let xTo = null;
let yTo = null;
let curCard = null;
let hideTimeout = null;
function getRotateOptions(card) {
const rotateAttr = card.getAttribute('data-rotate');
if (!rotateAttr) return { isReversed: false, rotation: -15 };
const match = rotateAttr.match(/positive-(\d+)deg/i) || rotateAttr.match(/(\d+)deg/i);
const deg = match ? parseInt(match[1], 10) : 15;
const isPositive = /positive/i.test(rotateAttr);
return { isReversed: isPositive, rotation: isPositive ? deg : -deg };
}
function updatePosition(e) {
if (!xTo || !yTo || !curCard) return;
const opts = getRotateOptions(curCard);
const x = opts.isReversed ? e.clientX - offsetX : e.clientX + offsetX;
xTo(x);
yTo(e.clientY + offsetY);
}
function scheduleHide() {
if (hideTimeout) clearTimeout(hideTimeout);
hideTimeout = setTimeout(function () {
hideTimeout = null;
const opts = curCard ? getRotateOptions(curCard) : { isReversed: false };
curCard = null;
gsap.to(preview, {
opacity: 0,
scale: opts.isReversed ? 1.2 : 0,
duration: 0.28,
ease: opts.isReversed ? 'power2.in' : 'power2.in',
onComplete: function () {
gsap.set(preview, { visibility: 'hidden' });
},
});
}, 120);
}
function showPreview(card, e) {
if (hideTimeout) {
clearTimeout(hideTimeout);
hideTimeout = null;
}
const src = card.getAttribute('data-img-award');
if (!src) return;
curCard = card;
img.src = src;
img.alt = card.querySelector('.card-award-title')?.textContent?.trim() || 'Award';
const opts = getRotateOptions(card);
if (opts.isReversed) {
gsap.set(preview, {
visibility: 'visible',
transformOrigin: 'right bottom',
rotation: opts.rotation,
x: e.clientX - offsetX,
y: e.clientY + offsetY,
xPercent: -100,
yPercent: -100,
scale: 0,
opacity: 0,
});
if (!xTo) xTo = gsap.quickTo(preview, 'x', { duration: 0.35, ease: 'power2.out' });
if (!yTo) yTo = gsap.quickTo(preview, 'y', { duration: 0.35, ease: 'power2.out' });
gsap.to(preview, {
opacity: 1,
scale: 1,
duration: 0.4,
ease: 'back.out(1.15)',
});
} else {
// Keep legacy behavior: right side of mouse, rotate -15deg, scale 0 -> 1
gsap.set(preview, {
visibility: 'visible',
transformOrigin: 'left bottom',
rotation: opts.rotation,
x: e.clientX + offsetX,
y: e.clientY + offsetY,
xPercent: 0,
yPercent: -100,
scale: 0,
opacity: 0,
});
if (!xTo) xTo = gsap.quickTo(preview, 'x', { duration: 0.35, ease: 'power2.out' });
if (!yTo) yTo = gsap.quickTo(preview, 'y', { duration: 0.35, ease: 'power2.out' });
gsap.to(preview, {
opacity: 1,
scale: 1,
duration: 0.4,
ease: 'back.out(1.15)',
});
}
}
cards.forEach(function (card) {
const src = card.getAttribute('data-img-award');
if (!src) return;
card.addEventListener('mouseenter', function (e) {
if (window.innerWidth < 768) return;
showPreview(card, e);
card.addEventListener('mousemove', updatePosition);
});
card.addEventListener('mouseleave', function (e) {
card.removeEventListener('mousemove', updatePosition);
var related = e.relatedTarget;
var isEnteringCard = related && related.closest && related.closest('.card-award');
if (isEnteringCard) return;
scheduleHide();
});
});
})();
}
////////////////////////////////////////////////////
// 15. panel pin section (at-panel-pin)
if (typeof gsap !== 'undefined') {
let pr = gsap.matchMedia();
pr.add('(min-width: 1199px)', () => {
let panels = document.querySelectorAll('.at-panel-pin');
if (panels.length > 0) {
let tl = gsap.timeline();
panels.forEach((section, index) => {
if (section) {
tl.to(section, {
scrollTrigger: {
trigger: section,
pin: section,
scrub: 1,
start: 'top 5%',
end: 'bottom 90%',
endTrigger: '.at-panel-pin-area',
pinSpacing: false,
// markers: true,
},
});
}
});
}
});
}
////////////////////////////////////////////////////
// 16. reveal-text
if ($('.reveal-text').length) {
var textheading = $('.reveal-text');
if (textheading.length === 0) return;
gsap.registerPlugin(SplitText);
textheading.each(function (index, el) {
el.split = new SplitText(el, {
type: 'lines,words,chars',
linesClass: 'split-line',
});
if (el.split && el.split.chars && el.split.chars.length > 0) {
if ($(el).hasClass('reveal-text')) {
gsap.set(el.split.chars, {
opacity: 0.4,
x: '-7',
});
}
el.anim = gsap.to(el.split.chars, {
scrollTrigger: {
trigger: el,
start: 'top 80%',
end: 'top 20%',
markers: false,
scrub: 1,
},
x: '0',
y: '0',
opacity: 1,
duration: 0.7,
stagger: 0.2,
});
}
});
}
////////////////////////////////////////////////////
// 17. webgl images hover animation
if ($('.at-hover-item').length) {
let hoverAnimation__do = function (t, n) {
let a = new hoverEffect({
parent: t.get(0),
intensity: t.data('intensity') || void 0,
speedIn: t.data('speedin') || void 0,
speedOut: t.data('speedout') || void 0,
easing: t.data('easing') || void 0,
hover: t.data('hover') || void 0,
image1: n.eq(0).attr('src'),
image2: n.eq(0).attr('src'),
displacementImage: t.data('displacement'),
imagesRatio: n[0].height / n[0].width,
hover: !1,
});
t.closest('.at-hover-item')
.on('mouseenter', function () {
a.next();
})
.on('mouseleave', function () {
a.previous();
});
};
let hoverAnimation = function () {
$('.at-hover-img').each(function () {
let n = $(this);
let e = n.find('img');
let i = e.eq(0);
i[0].complete
? hoverAnimation__do(n, e)
: i.on('load', function () {
hoverAnimation__do(n, e);
});
});
};
hoverAnimation();
}
////////////////////////////////////////////////////
// 18. Section to title zoom and item uper
if (typeof gsap !== 'undefined') {
gsap.matchMedia().add('(min-width: 1200px)', () => {
const portfolioArea = document.querySelector('.portfolio-area');
const portfolioText = document.querySelector('.portfolio-text');
if (portfolioArea && portfolioText) {
// Timeline
let portfolioline = gsap.timeline({
scrollTrigger: {
trigger: portfolioArea,
start: 'top center-=200',
pin: portfolioText,
end: 'bottom bottom+=10',
markers: false,
pinSpacing: false,
scrub: 1,
// markers: true,
},
});
// Get container dimensions for diagonal movement
const areaRect = portfolioArea.getBoundingClientRect();
const textRect = portfolioText.getBoundingClientRect();
// Calculate end positions (bottom-right corner minus text size)
const endX = areaRect.width - textRect.width - 50;
const endY = areaRect.height - textRect.height - 50;
portfolioline.fromTo(portfolioText, { x: 0, y: 0, scale: 1 }, { x: endX, y: endY, scale: 1, duration: 1, ease: 'none' });
// Opacity scroll animation
gsap.to(portfolioText, {
scrollTrigger: {
trigger: portfolioArea,
start: 'top center-=100',
end: 'bottom bottom+=10',
scrub: 1,
},
opacity: 0,
});
}
});
}
////////////////////////////////////////////////////
// 19. anim-zoomin + gallery isotope
if (typeof gsap !== 'undefined') {
$('.anim-zoomin').each(function () {
// Add wrap
.
$(this).wrap('
');
// Add overflow hidden.
$('.anim-zoomin-wrap').css({ overflow: 'hidden' });
let $this = $(this);
let $asiWrap = $this.parents('.anim-zoomin-wrap');
if ($asiWrap.length && $this.length) {
let tp_ZoomIn = gsap.timeline({
scrollTrigger: {
trigger: $asiWrap[0],
start: 'top 100%',
markers: false,
},
});
tp_ZoomIn.from($this[0], { duration: 2, autoAlpha: 0, scale: 1.2, ease: Power2.easeOut, clearProps: 'all' });
}
});
}
// Home 8 hero — per-photo scroll parallax (shared scroll range + different Y range + ease = visible speed contrast)
// Note: `end: "+=px"` is unreliable with ScrollSmoother; keep `bottom top` on the hero trigger.
if (typeof gsap !== 'undefined' && typeof ScrollTrigger !== 'undefined') {
const reduceMotion = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
const heroSec = document.querySelector('.sec-1-home-8');
const photoBoxes = heroSec ? heroSec.querySelectorAll('.h8-photo-box') : null;
if (!reduceMotion && heroSec && photoBoxes && photoBoxes.length) {
const defaultRanges = [28, 110, 52, 92, 24, 125];
const defaultEases = ['power1.out', 'power3.in', 'sine.inOut', 'expo.in', 'none', 'power2.inOut'];
photoBoxes.forEach((box, index) => {
const rawRange = box.getAttribute('data-h8-parallax-range');
let range = rawRange != null && rawRange !== '' ? parseFloat(rawRange) : defaultRanges[index % defaultRanges.length];
if (!Number.isFinite(range) || range <= 0) {
range = defaultRanges[index % defaultRanges.length];
}
const rawEase = box.getAttribute('data-h8-parallax-ease');
const easeName = rawEase != null && rawEase !== '' ? String(rawEase).trim() : defaultEases[index % defaultEases.length];
if (window.innerWidth < 768) {
range *= 0.55;
}
const dir = index % 2 === 0 ? 1 : -1;
const yStart = (range * 0.5) * dir;
const yEnd = (-range * 0.5) * dir;
gsap.fromTo(
box,
{ y: yStart },
{
y: yEnd,
ease: easeName,
scrollTrigger: {
trigger: heroSec,
start: 'top bottom',
end: 'bottom top',
scrub: 0.85,
invalidateOnRefresh: true,
},
},
);
});
}
}
// Home 8 sec-6 — cards parallax (different ranges = different scroll “speed”)
if (typeof gsap !== 'undefined' && typeof ScrollTrigger !== 'undefined') {
const reduceMotion = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
const sec6 = document.querySelector('.sec-6-home-8');
const cards = sec6 ? sec6.querySelectorAll('.sec-6-home-8__card') : null;
if (!reduceMotion && sec6 && cards && cards.length) {
const defaultRanges = [28, 120, 42, 160, 34, 140, 80];
const defaultEases = ['power1.out', 'expo.inOut', 'sine.inOut', 'power3.in', 'none', 'circ.inOut', 'power2.inOut'];
cards.forEach((card, index) => {
const raw = card.getAttribute('data-sec6-parallax-range');
let range = raw != null && raw !== '' ? parseFloat(raw) : defaultRanges[index % defaultRanges.length];
if (!Number.isFinite(range) || range <= 0) {
range = defaultRanges[index % defaultRanges.length];
}
const rawEase = card.getAttribute('data-sec6-parallax-ease');
const easeName = rawEase != null && rawEase !== '' ? String(rawEase).trim() : defaultEases[index % defaultEases.length];
if (window.innerWidth < 768) {
range *= 0.5;
}
const dir = index % 2 === 0 ? 1 : -1;
const yStart = range * dir;
const yEnd = -range * dir;
gsap.fromTo(
card,
{ y: yStart },
{
y: yEnd,
ease: easeName,
scrollTrigger: {
trigger: sec6,
start: 'top bottom',
end: 'bottom top',
scrub: 0.75,
invalidateOnRefresh: true,
},
},
);
});
}
}
// Home 8 sec-8 — pin stage, keep center at 20% viewport, scroll groups upward, then continue section scroll
if (typeof gsap !== 'undefined' && typeof ScrollTrigger !== 'undefined') {
const reduceMotion = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
const sec8 = document.querySelector('.sec-8-home-8');
const stage8 = sec8 ? sec8.querySelector('.sec-8-home-8__stage') : null;
const center8 = sec8 ? sec8.querySelector('.sec-8-home-8__center') : null;
const groups8 = sec8 ? Array.from(sec8.querySelectorAll('.sec-8-home-8__group')) : [];
if (!reduceMotion && sec8 && stage8 && center8 && groups8.length) {
const isNarrow = window.innerWidth < 992;
if (!isNarrow) {
const getMaxBottom = () => {
let max = 0;
groups8.forEach((el) => {
const b = el.offsetTop + el.offsetHeight;
if (b > max) max = b;
});
return max;
};
const getScrollDistance = () => {
const maxBottom = getMaxBottom();
// Only scroll as much as needed to move the last card set out.
// `center8.offsetTop` is the center's top position inside the stage.
const centerTop = center8.offsetTop || 0;
const base = Math.max(0, maxBottom - centerTop);
return Math.round(base);
};
const build = () => {
const scrollDistance = getScrollDistance();
gsap.set(center8, { willChange: 'transform' });
gsap.set(groups8, { willChange: 'transform' });
// Pin the whole stage so the center never drifts outside the section background.
// The center is already at `top 10%` at the moment the trigger starts,
// so avoid mutating its inline styles (prevents a visual "jump").
// Use a timeline so groups scrub while stage is pinned
gsap.timeline({
scrollTrigger: {
trigger: center8,
start: 'top 10%',
end: `+=${scrollDistance}`,
pin: stage8,
pinSpacing: true,
scrub: 1,
anticipatePin: 1,
invalidateOnRefresh: true,
},
}).to(groups8, { y: -scrollDistance, ease: 'none' }, 0);
};
build();
// Refresh on resize so distances stay correct
window.addEventListener('resize', () => {
ScrollTrigger.refresh();
});
}
}
}
$('.gallery').isotope({
itemSelector: '.items'
});
////////////////////////////////////////////////////
// 20. scale-img-from-to
if (typeof gsap !== 'undefined') {
const scaleImage = document.querySelectorAll('.scale-img-from-to');
scaleImage.forEach((section) => {
if (!section) return;
var value1 = section.getAttribute('data-value-1');
var value2 = section.getAttribute('data-value-2');
if (window.innerWidth < 1200) {
value1 = Math.max(0.95, value1);
}
gsap.fromTo(
section,
{
ease: 'sine',
scale: value1,
},
{
scale: value2,
scrollTrigger: {
trigger: section,
scrub: true,
toggleActions: 'play none none reverse',
},
},
);
});
}
////////////////////////////////////////////////////
// 21. text-scale-anim
if (typeof gsap !== 'undefined') {
const headings = document.querySelectorAll('.text-scale-anim');
headings.forEach((heading) => {
if (!heading) return;
const textNodes = [];
heading.childNodes.forEach((node) => {
if (node.nodeType === Node.TEXT_NODE) {
node.textContent.split(' ').forEach((word, index, array) => {
const wordSpan = document.createElement('span');
wordSpan.classList.add('at-word-span');
word.split('').forEach((letter) => {
const letterSpan = document.createElement('span');
letterSpan.classList.add('at-letter-span');
letterSpan.textContent = letter;
wordSpan.appendChild(letterSpan);
});
textNodes.push(wordSpan);
if (index < array.length - 1) {
textNodes.push(document.createTextNode(' '));
}
});
} else if (node.nodeType === Node.ELEMENT_NODE) {
textNodes.push(node.cloneNode(true));
}
});
heading.innerHTML = '';
textNodes.forEach((node) => heading.appendChild(node));
const letters = heading.querySelectorAll('.at-letter-span');
letters.forEach((letter, index) => {
if (!letter) return;
$(letter).on('mouseenter', () => {
// center letter
gsap.to(letter, {
scaleY: 1.6,
y: '-24%',
duration: 0.4,
ease: 'sine',
});
// left neighbor
const prev = letters[index - 1];
if (prev) {
gsap.to(prev, {
scaleY: 1.3,
y: '-12%',
duration: 0.4,
ease: 'sine',
});
}
// right neighbor
const next = letters[index + 1];
if (next) {
gsap.to(next, {
scaleY: 1.3,
y: '-12%',
duration: 0.4,
ease: 'sine',
});
}
});
$(letter).on('mouseleave', () => {
// reset center letter
gsap.to(letter, {
scaleY: 1,
y: '0%',
duration: 0.4,
ease: 'sine',
});
// reset left and right neighbors
const prev = letters[index - 1];
const next = letters[index + 1];
[prev, next].forEach((el) => {
if (el) {
gsap.to(el, {
scaleY: 1,
y: '0%',
duration: 0.4,
ease: 'sine',
});
}
});
});
});
});
}
////////////////////////////////////////////////////
// 22. text-scale-anim-2
if (typeof gsap !== 'undefined') {
const headings2 = document.querySelectorAll('.text-scale-anim-2');
headings2.forEach((heading) => {
if (!heading) return;
const textNodes = [];
heading.childNodes.forEach((node) => {
if (node.nodeType === Node.TEXT_NODE) {
node.textContent.split(' ').forEach((word, index, array) => {
const wordSpan = document.createElement('span');
wordSpan.classList.add('at-word-span');
word.split('').forEach((letter) => {
const letterSpan = document.createElement('span');
letterSpan.classList.add('at-letter-span');
letterSpan.textContent = letter;
wordSpan.appendChild(letterSpan);
});
textNodes.push(wordSpan);
if (index < array.length - 1) {
textNodes.push(document.createTextNode(' '));
}
});
} else if (node.nodeType === Node.ELEMENT_NODE) {
textNodes.push(node.cloneNode(true));
}
});
heading.innerHTML = '';
textNodes.forEach((node) => heading.appendChild(node));
const letters = heading.querySelectorAll('.at-letter-span');
letters.forEach((letter, index) => {
if (!letter) return;
$(letter).on('mouseenter', () => {
// center letter
gsap.to(letter, {
scaleY: 1.3,
y: '-14%',
duration: 0.4,
ease: 'sine',
});
// left neighbor
const prev = letters[index - 1];
if (prev) {
gsap.to(prev, {
scaleY: 1.1,
y: '-5%',
duration: 0.4,
ease: 'sine',
});
}
// right neighbor
const next = letters[index + 1];
if (next) {
gsap.to(next, {
scaleY: 1.1,
y: '-5%',
duration: 0.4,
ease: 'sine',
});
}
});
$(letter).on('mouseleave', () => {
// reset center letter
gsap.to(letter, {
scaleY: 1,
y: '0%',
duration: 0.4,
ease: 'sine',
});
// reset left and right neighbors
const prev = letters[index - 1];
const next = letters[index + 1];
[prev, next].forEach((el) => {
if (el) {
gsap.to(el, {
scaleY: 1,
y: '0%',
duration: 0.4,
ease: 'sine',
});
}
});
});
});
});
}
////////////////////////////////////////////////////
// 23. scroll-scale-up-img
if (typeof gsap !== 'undefined') {
document.querySelectorAll('.scale-up-img').forEach((section) => {
if (!section) return;
const scaleUpElement = section.querySelector('.scale-up');
if (!scaleUpElement) return;
let tl = gsap.timeline({
scrollTrigger: {
trigger: section,
start: 'top bottom',
end: 'bottom center',
scrub: 1,
markers: false,
},
});
tl.to(scaleUpElement, {
scale: 1.15,
duration: 1,
});
});
}
////////////////////////////////////////////////////
// 24. postbox-scroll-zoom (20% initial, zoom on scroll to center, play btn near end)
if (typeof gsap !== 'undefined' && typeof ScrollTrigger !== 'undefined') {
document.querySelectorAll('.postbox-scroll-zoom').forEach((section) => {
const itemwrap = section.querySelector('.postbox-item');
const thumb = section.querySelector('.postbox-thumb');
const playBtn = section.querySelector('.postbox-scroll-zoom-play');
if (!thumb || !playBtn) return;
const tl = gsap.timeline({
scrollTrigger: {
trigger: itemwrap,
start: 'top top',
end: 'bottom top',
pin: true,
scrub: 1,
toggleActions: 'play none none reverse',
markers: false,
},
});
tl.fromTo(
thumb,
{
width: '20%',
height: '20%',
duration: 1,
ease: 'none',
},
{
width: '100%',
height: '100%',
duration: 1,
ease: 'none',
},
'<',
);
tl.fromTo(playBtn, { opacity: 0, scale: 0.5 }, { opacity: 1, scale: 1, ease: 'none' }, 0.78);
tl.call(() => section.classList.add('postbox-scroll-zoom-ready'), [], 0.78);
});
}
////////////////////////////////////////////////////
// 25. Sticky Header Js
windowOn.on('scroll', function () {
let scroll = windowOn.scrollTop();
if (scroll < 20) {
$('#header-sticky').removeClass('header-sticky');
} else {
$('#header-sticky').addClass('header-sticky');
}
});
////////////////////////////////////////////////////
// 26. scroll-brand-logo
if (typeof gsap !== 'undefined') {
const LOGO_HEIGHT = window.matchMedia('(max-width: 991px)').matches ? 60 : 70;
document.querySelectorAll('.at-brand-scroll .at-brand-item .brand').forEach((brandEl) => {
const slides = Array.from(brandEl.querySelectorAll('.brand-logo-slide'));
if (!slides.length) return;
const viewport = document.createElement('div');
viewport.className = 'brand-logo-viewport';
const track = document.createElement('div');
track.className = 'brand-logo-track';
brandEl.insertBefore(viewport, slides[0]);
viewport.appendChild(track);
const shuffled = slides.slice().sort(() => Math.random() - 0.5);
shuffled.forEach((s) => track.appendChild(s));
const clone = shuffled[0].cloneNode(true);
track.appendChild(clone);
gsap.set(track, { y: 0 });
const initialDelay = Math.random() * 1.5;
const scrollDuration = 1;
const pauseDuration = 1 + Math.random() * 0.8;
const tl = gsap.timeline({ repeat: -1 });
tl.to({}, { duration: initialDelay });
for (let i = 0; i < shuffled.length; i++) {
tl.to({}, { duration: pauseDuration });
const yNext = -(i + 1) * LOGO_HEIGHT;
tl.to(track, { y: yNext, duration: scrollDuration, ease: 'power2.inOut' });
}
tl.add(() => gsap.set(track, { y: 0 }));
});
}
////////////////////////////////////////////////////
// 27. move-up
if (typeof gsap !== 'undefined') {
const moveUp = document.querySelectorAll('.move-up');
if (moveUp.length > 0 && moveUp[0]) {
gsap.to(Array.from(moveUp), {
transformOrigin: 'top',
y: '-500px',
duration: 1,
ease: 'power2.out',
scrollTrigger: {
trigger: moveUp[0],
start: 'top center',
scrub: 1,
},
});
}
}
////////////////////////////////////////////////////
// 28. at-about-svg-wrap
if (typeof gsap !== 'undefined') {
const aboutSvg = document.querySelectorAll('.at-about-svg-wrap');
aboutSvg.forEach((svg) => {
if (!svg) return;
const svgLeft = svg.querySelector('svg:nth-child(1)');
const svgCenter = svg.querySelector('svg:nth-child(2)');
const svgRight = svg.querySelector('svg:nth-child(3)');
if (svgLeft) {
gsap.from(svgLeft, {
transformOrigin: 'left center',
duration: 1,
ease: 'power2.out',
x: '-100px',
scrollTrigger: {
trigger: svg,
start: 'top 90%',
end: 'bottom center',
scrub: 1,
},
});
}
if (svgCenter) {
gsap.from(svgCenter, {
transformOrigin: 'center center',
duration: 1,
ease: 'power2.out',
y: '-100px',
scrollTrigger: {
trigger: svg,
start: 'top 90%',
end: 'bottom center',
scrub: 1,
},
});
}
if (svgRight) {
gsap.from(svgRight, {
transformOrigin: 'right center',
duration: 1,
ease: 'power2.out',
x: '100px',
scrollTrigger: {
trigger: svg,
start: 'top 90%',
end: 'bottom center',
scrub: 1,
},
});
}
});
}
////////////////////////////////////////////////////
// 29. at-title-text
function initProject() {
const titleElements = document.querySelectorAll('.at-title-text');
if (!titleElements.length) return;
titleElements.forEach((textEl) => {
const text = textEl.textContent.trim();
textEl.setAttribute('aria-label', text); // Accessibility
const html = [...text]
.map((char, i) => {
const safeChar = char === ' ' ? ' ' : char;
return `
${safeChar}`;
})
.join('');
textEl.innerHTML = html;
});
}
initProject();
////////////////////////////////////////////////////
// 30. panel pin section (mg-portfolio-pin)
if (typeof gsap !== 'undefined') {
let pr2 = gsap.matchMedia();
pr2.add('(min-width: 1199px)', () => {
let panels = document.querySelectorAll('.mg-portfolio-pin');
if (panels.length > 0) {
let tl = gsap.timeline();
panels.forEach((section, index) => {
if (section) {
tl.to(section, {
scrollTrigger: {
trigger: section,
pin: section,
scrub: 1,
start: 'top 5%',
end: 'bottom 90%',
endTrigger: '.mg-portfolio-area',
pinSpacing: false,
// markers: true,
},
});
}
});
}
});
}
////////////////////////////////////////////////////
// 31. panel pin section (at-header-pin)
if (typeof gsap !== 'undefined') {
let pr2 = gsap.matchMedia();
pr2.add('(min-width: 1199px)', () => {
let panels = document.querySelectorAll('.at-header-pin');
if (panels.length > 0) {
let tl = gsap.timeline();
panels.forEach((section, index) => {
if (section) {
tl.to(section, {
scrollTrigger: {
trigger: section,
pin: section,
scrub: 1,
start: '30% top',
end: 'bottom bottom',
endTrigger: '.home-2-section-13',
pinSpacing: false,
// markers: true,
},
});
}
});
}
});
}
////////////////////////////////////////////////////
// 32. section-fix (pin section-title + stacking cards)
if (typeof gsap !== 'undefined' && typeof ScrollTrigger !== 'undefined') {
function initSectionFix() {
const sectionFixList = document.querySelectorAll('.section-fix');
if (!sectionFixList.length) return;
sectionFixList.forEach((sectionFix) => {
const sectionTitlePin = sectionFix.querySelector('.section-title-pin');
const scrollSectionEl = sectionFix.querySelector('.scroll-section.vertical-section');
if (!scrollSectionEl || !sectionTitlePin) return;
const wrapper = scrollSectionEl.querySelector('.wrapper');
if (!wrapper) return;
const items = wrapper.querySelectorAll('.item');
if (!items.length) return;
items.forEach((item, index) => {
if (index !== 0) gsap.set(item, { yPercent: 100 });
});
const scrollDistance = items.length * 50;
const navList = sectionFix.querySelector('.navigation-active-item');
const navItems = navList ? navList.querySelectorAll('li .item') : [];
const tl = gsap.timeline({
scrollTrigger: {
trigger: sectionFix,
pin: true,
start: 'top top',
end: () => `+=${scrollDistance}%`,
scrub: 1,
invalidateOnRefresh: true,
onUpdate: (self) => {
const progress = Math.min(Math.max(self.progress, 0), 0.9999);
const index = Math.min(Math.floor(progress * items.length), items.length - 1);
items.forEach((el, i) => el.classList.toggle('active', i === index));
navItems.forEach((el, i) => el.classList.toggle('active', i === index));
},
},
defaults: { ease: 'none', duration: 1 },
});
items.forEach((item, index) => {
tl.to(item, { scale: 0.9 });
if (items[index + 1]) {
tl.to(items[index + 1], { yPercent: 0 }, '<');
}
});
function updateActiveByProgress() {
if (tl.scrollTrigger && tl.scrollTrigger.isActive()) {
const progress = Math.min(Math.max(tl.scrollTrigger.progress, 0), 0.9999);
const index = Math.min(Math.floor(progress * items.length), items.length - 1);
items.forEach((el, i) => el.classList.toggle('active', i === index));
navItems.forEach((el, i) => el.classList.toggle('active', i === index));
}
}
ScrollTrigger.addEventListener('scroll', updateActiveByProgress);
});
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initSectionFix);
} else {
initSectionFix();
}
window.addEventListener('load', function () {
if (typeof ScrollTrigger !== 'undefined') ScrollTrigger.refresh();
});
}
////////////////////////////////////////////////////
// 33. section-title-pin
if (typeof gsap !== 'undefined') {
let pr3 = gsap.matchMedia();
pr3.add('(min-width: 1400px)', () => {
let panels = document.querySelectorAll('.section-title-pin');
if (panels.length > 0) {
panels.forEach((section) => {
if (!section || section.closest('.section-fix')) return;
gsap.to(section, {
scrollTrigger: {
trigger: section,
pin: section,
scrub: 1,
start: 'top bottom+=200',
endTrigger: section.closest('section') || 'body',
end: 'bottom top',
pinSpacing: false,
},
});
});
}
});
}
////////////////////////////////////////////////////
// 34. data-countdown
if ($("[data-countdown]").length > 0) {
let countdown = $("[data-countdown]");
countdown.each(function () {
var $this = $(this);
var dateStr = $this.attr("data-countdown") || $this.data("countdown");
if (!dateStr) return;
var finalDate = new Date(dateStr.replace(/\//g, "-"));
if (isNaN(finalDate.getTime())) return;
function updateCountdown() {
var now = new Date();
var diff = finalDate.getTime() - now.getTime();
if (diff <= 0) {
$this.html('
0 days 0 hours 0 mins 0 secs ');
clearInterval(timer);
return;
}
var d = Math.floor(diff / (1000 * 60 * 60 * 24));
var h = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
var m = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60));
var s = Math.floor((diff % (1000 * 60)) / 1000);
$this.html(
'
' + d + ' days ' +
'
' + h + ' hours ' +
'
' + m + ' mins ' +
'
' + s + ' secs '
);
}
updateCountdown();
var timer = setInterval(updateCountdown, 1000);
});
}
////////////////////////////////////////////////////
// 35. at-item-anime marque
if (typeof gsap !== 'undefined') {
if ($('.at-item-anime').length > 0) {
const marqueElements = document.querySelectorAll('.at-item-anime.marque');
const triggerElement = document.querySelector('.at-item-anime-area');
if (marqueElements.length > 0 && triggerElement) {
gsap.set(Array.from(marqueElements), {
x: '35%',
});
gsap.timeline({
scrollTrigger: {
trigger: triggerElement,
start: '-1000 0%',
end: 'bottom 0%',
scrub: true,
invalidateOnRefresh: true,
},
}).to(Array.from(marqueElements), {
x: '-200%',
});
}
}
// at-item-anime marque-2 (reverse direction)
// Used by Home 8 sec-7 row 2: move left-to-right on scroll.
if ($('.at-item-anime-2').length > 0) {
const marqueElements2 = document.querySelectorAll('.at-item-anime-2.marque');
const triggerElement2 = document.querySelector('.at-item-anime-area-2');
if (marqueElements2.length > 0 && triggerElement2) {
gsap.set(Array.from(marqueElements2), {
x: '-35%',
});
gsap.timeline({
scrollTrigger: {
trigger: triggerElement2,
start: '-1000 0%',
end: 'bottom 0%',
scrub: true,
invalidateOnRefresh: true,
},
}).to(Array.from(marqueElements2), {
x: '200%',
});
}
}
}
////////////////////////////////////////////////////
// 36. at-pricing-area
function tabtable_active() {
const $elements = {
monthly: $('#at-nav-monthly'),
yearly: $('#at-nav-yearly'),
switcher: $('#at-switcher-input'),
tabMonthly: $('#at-tab-monthly'),
tabYearly: $('#at-tab-yearly'),
};
const setActive = (isMonthly) => {
$elements.switcher.prop('checked', isMonthly);
$elements.monthly.toggleClass('is-active', isMonthly);
$elements.yearly.toggleClass('is-active', !isMonthly);
$elements.tabMonthly.toggleClass('at-tab-hide', !isMonthly);
$elements.tabYearly.toggleClass('at-tab-hide', isMonthly);
};
[$elements.monthly, $elements.yearly].forEach(($el) => $el.on('click', () => setActive($el.is($elements.monthly))));
$elements.switcher.on('click', () => setActive(!$elements.monthly.hasClass('is-active')));
}
if ($('#at-nav-monthly').length) tabtable_active();
////////////////////////////////////////////////////
// 37. auto update year (.update-year)
$('.update-year').text(new Date().getFullYear());
////////////////////////////////////////////////////
// 38. fade-class-active
if (typeof gsap !== 'undefined' && $('.at_fade_anim').length > 0) {
gsap.utils.toArray('.at_fade_anim').forEach((item) => {
if (!item) return;
const tp_fade_offset = parseFloat(item.getAttribute('data-fade-offset')) || 20;
const tp_duration_value = parseFloat(item.getAttribute('data-duration')) || 0.75;
const tp_fade_direction = item.getAttribute('data-fade-from') || 'bottom';
const tp_onscroll_attr = item.getAttribute('data-on-scroll');
const tp_onscroll_value = tp_onscroll_attr === null ? 1 : parseInt(tp_onscroll_attr, 10);
const tp_delay_parsed = parseFloat(item.getAttribute('data-delay'));
const tp_delay_value = isNaN(tp_delay_parsed) ? 0.15 : tp_delay_parsed;
const tp_ease_value = item.getAttribute('data-ease') || 'power2.out';
const tp_start_value = item.getAttribute('data-start') || '85%';
const tp_from_vars = {
opacity: 0,
x: tp_fade_direction == 'left' ? -tp_fade_offset : tp_fade_direction == 'right' ? tp_fade_offset : 0,
y: tp_fade_direction == 'top' ? -tp_fade_offset : tp_fade_direction == 'bottom' ? tp_fade_offset : 0,
};
const tp_to_vars = {
opacity: 1,
x: 0,
y: 0,
ease: tp_ease_value,
duration: tp_duration_value,
delay: tp_delay_value,
};
if (tp_onscroll_value == 1) {
tp_to_vars.scrollTrigger = {
trigger: item,
start: 'top ' + tp_start_value,
toggleActions: 'play none none none',
};
}
gsap.fromTo(item, tp_from_vars, tp_to_vars);
});
}
////////////////////////////////////////////////////
// 39. character Animation
if (typeof gsap !== 'undefined' && typeof SplitText !== 'undefined' && $('.at-char-animation').length > 0) {
let char_come = gsap.utils.toArray('.at-char-animation');
char_come.forEach((splitTextLine) => {
if (!splitTextLine) return;
const tl = gsap.timeline({
scrollTrigger: {
trigger: splitTextLine,
start: 'top 90%',
end: 'bottom 60%',
scrub: false,
markers: false,
toggleActions: 'play none none none',
},
});
const itemSplitted = new SplitText(splitTextLine, { type: 'chars, words' });
if (!itemSplitted || !itemSplitted.chars || itemSplitted.chars.length === 0) return;
gsap.set(splitTextLine, { perspective: 300 });
itemSplitted.split({ type: 'chars, words' });
tl.from(itemSplitted.chars, {
duration: 1,
delay: 0.5,
x: 100,
autoAlpha: 0,
stagger: 0.05,
});
});
}
////////////////////////////////////////////////////
// 40. Parallax Js
if ($('.scene').length > 0) {
$('.scene').parallax({
scalarX: 5.0,
scalarY: 5.0,
});
}
if ($('.scene-y').length > 0) {
$('.scene-y').parallax({
scalarY: 5.0,
scalarX: 0,
});
}
////////////////////////////////////////////////////
// 41. Archive blog sidebar_right sticky
if (typeof gsap !== 'undefined' && typeof ScrollTrigger !== 'undefined') {
var archiveSection = document.querySelector('.sec-1-archive-2');
if (archiveSection) {
var archiveSidebar = archiveSection.querySelector('.sidebar_right');
var archiveContentCol = archiveSection.querySelector('.row.pt-80 .col-lg-8');
if (archiveSidebar && archiveContentCol) {
gsap.matchMedia().add('(min-width: 992px)', function () {
gsap.timeline({
scrollTrigger: {
trigger: archiveContentCol,
start: 'top 50px',
end: 'bottom bottom',
scrub: 1,
pin: archiveSidebar,
pinSpacing: false,
invalidateOnRefresh: true,
},
});
});
}
}
}
////////////////////////////////////////////////////
// 42. ripple-image
imagesLoaded('.ripple-image img', { background: true }, () => {
$('.ripple-image').each(function () {
var $container = $(this);
var $img = $container.find('img').first();
var imgURL = $img.attr('src');
$container.css({
'background-image': 'url(' + imgURL + ')',
'background-size': 'cover',
'background-position': 'center center',
});
$container.ripples({
resolution: 400,
perturbance: 0.03,
imageUrl: imgURL,
});
$img.hide();
});
});
////////////////////////////////////////////////////
// 43. Odometer counter
if ($('.odometer').length > 0) {
$('.odometer').each(function () {
var $this = $(this);
var countNumber = $this.attr('data-count');
$this.html('0');
$this.appear(
function () {
var $element = $(this);
if (!$element.hasClass('odometer-animated')) {
$element.addClass('odometer-animated');
setTimeout(function () {
$element.html(countNumber);
}, 100);
}
},
{
accY: -50,
},
);
});
}
////////////////////////////////////////////////////
// 44. about-me-slider-active
var portfolio = new Swiper('.about-me-slider-active', {
slidesPerView: 2,
loop: true,
autoplay: false,
arrow: false,
spaceBetween: 30,
breakpoints: {
'992': {
slidesPerView: 2,
},
'768': {
slidesPerView: 1,
},
'576': {
slidesPerView: 1,
},
'0': {
slidesPerView: 1,
},
},
a11y: false,
});
////////////////////////////////////////////////////
// 45. slider-testimonial-thumbs
if ($('.slider-testimonial-thumbs').length > 0) {
const thumbsWrap = document.querySelector('.slider-testimonial-thumbs');
const sectionTrigger = thumbsWrap.closest('section');
const thumb1 = thumbsWrap.querySelector('.swiper-slide:nth-child(1)');
const thumb2 = thumbsWrap.querySelector('.swiper-slide:nth-child(2)');
const thumb4 = thumbsWrap.querySelector('.swiper-slide:nth-child(4)');
const thumb5 = thumbsWrap.querySelector('.swiper-slide:nth-child(5)');
const mmThumbs = gsap.matchMedia();
const thumbs = [thumb1, thumb2, thumb4, thumb5].filter(Boolean);
// xs (< 575px): no effect, keep thumbs at original position (match $xs)
mmThumbs.add('(max-width: 574.98px)', () => {
gsap.set(thumbs, { clearProps: 'x,y' });
return () => {};
});
// Mobile / tablet ($sm + $md: 576px – 991px)
mmThumbs.add('(min-width: 576px) and (max-width: 1399px)', () => {
const tl = gsap.timeline({
scrollTrigger: {
trigger: sectionTrigger,
start: 'top 50%',
end: 'bottom 70%',
scrub: 1,
invalidateOnRefresh: true,
},
});
tl.to(thumb1, { x: -70, y: 20, duration: 1 }, 0).to(thumb2, { x: -80, y: -60, duration: 1 }, 0).to(thumb4, { x: 70, y: -86, duration: 1 }, 0).to(thumb5, { x: 65, y: 15, duration: 1 }, 0);
return () => tl.kill();
});
// Desktop (>= 992px) – match $lg
mmThumbs.add('(min-width: 1400px)', () => {
const tl = gsap.timeline({
scrollTrigger: {
trigger: sectionTrigger,
start: 'top 50%',
end: 'bottom 70%',
scrub: 1,
invalidateOnRefresh: true,
},
});
tl.to(thumb1, { x: -500, y: 200, duration: 1 }, 0).to(thumb2, { x: -200, y: -100, duration: 1 }, 0).to(thumb4, { x: 300, y: -70, duration: 1 }, 0).to(thumb5, { x: 500, y: 200, duration: 1 }, 0);
return () => tl.kill();
});
}
////////////////////////////////////////////////////
// 46. Carousel Ticker
function carauselScroll() {
$('.carouselTicker-left').each(function () {
$(this).carouselTicker({
direction: 'prev',
speed: 1,
delay: 40,
});
});
$('.carouselTicker-right').each(function () {
$(this).carouselTicker({
direction: 'next',
speed: 1,
delay: 40,
});
});
}
carauselScroll();
////////////////////////////////////////////////////
// 46.1 GSAP Marquee (continuous, bidirectional)
// Usage:
//
function initGsapMarquee() {
if (typeof gsap === 'undefined') return;
const marquees = document.querySelectorAll('.at-marquee');
if (!marquees.length) return;
marquees.forEach((marquee) => {
const track = marquee.querySelector('.at-marquee__track');
const group = marquee.querySelector('.at-marquee__group');
if (!track || !group) return;
// Ensure we have at least 2 groups for seamless wrap
if (track.querySelectorAll('.at-marquee__group').length < 2) {
track.appendChild(group.cloneNode(true));
}
const direction = (marquee.dataset.direction || 'left').toLowerCase();
const speed = Math.max(10, parseFloat(marquee.dataset.speed || '80')); // px/sec
const measure = () => {
const w = group.offsetWidth || group.getBoundingClientRect().width || group.scrollWidth || 0;
return w;
};
const start = () => {
const groupWidth = measure();
if (!groupWidth || !Number.isFinite(groupWidth)) return;
gsap.killTweensOf(track);
const dur = groupWidth / speed;
// No gsap.utils.wrap — repeat:-1 already snaps from → to; with two
// identical groups side-by-side the jump at cycle end is invisible.
if (direction === 'right') {
gsap.fromTo(
track,
{ x: 0 },
{
x: groupWidth,
duration: dur,
ease: 'none',
repeat: -1,
}
);
} else {
gsap.fromTo(
track,
{ x: 0 },
{
x: -groupWidth,
duration: dur,
ease: 'none',
repeat: -1,
}
);
}
};
// Measure after layout/fonts (fixes width: 0 on first paint)
requestAnimationFrame(() => {
requestAnimationFrame(start);
});
window.addEventListener('resize', start);
});
}
initGsapMarquee();
////////////////////////////////////////////////////
// 47. customSwiper / slider-testimonial
function customSwiper() {
const sliderTestimonial = new Swiper('.slider-testimonial', {
slidesPerView: 4,
spaceBetween: 35,
slidesPerGroup: 1,
centeredSlides: true,
loop: true,
autoplay: {
delay: 5000,
},
breakpoints: {
1200: {
slidesPerView: 3,
},
992: {
slidesPerView: 2,
},
768: {
slidesPerView: 1,
},
576: {
slidesPerView: 1,
},
0: {
slidesPerView: 1,
},
},
navigation: {
nextEl: '.swiper-btn-next',
prevEl: '.swiper-btn-prev',
},
});
var sliderTestimonialThumbs = new Swiper('.slider-testimonial-thumbs', {
spaceBetween: 0,
slidesPerView: 5,
freeMode: true,
loop: true,
});
const sliderTestimonial2 = new Swiper('.slider-testimonial-2', {
slidesPerView: 1,
slidesPerGroup: 1,
centeredSlides: true,
loop: true,
autoplay: {
delay: 5000,
},
thumbs: {
swiper: sliderTestimonialThumbs,
},
navigation: {
nextEl: '.swiper-btn-next',
prevEl: '.swiper-btn-prev',
},
});
}
customSwiper();
////////////////////////////////////////////////////
// 48. scroll-move-up animation
if (typeof gsap !== 'undefined' && $('.scroll-move-up').length > 0) {
let scrollMoveUp = document.querySelectorAll('.scroll-move-up');
scrollMoveUp.forEach((scrollMoveUp) => {
if (!scrollMoveUp) return;
gsap.to(scrollMoveUp, {
y: -100,
duration: 1.5,
scrollTrigger: {
trigger: scrollMoveUp,
start: 'top 70%',
scrub: 1,
markers: false,
},
});
});
}
////////////////////////////////////////////////////
// 49. scroll-move-up2 animation
if (typeof gsap !== 'undefined' && $('.scroll-move-up2').length > 0) {
gsap.matchMedia().add('(min-width: 768px)', () => {
let items = document.querySelectorAll('.scroll-move-up2');
items.forEach((item) => {
if (!item) return;
gsap.from(item, 1.5, {
y: 80,
opacity: 0,
duration: 0.8,
ease: 'power4.out',
scrollTrigger: {
trigger: item,
start: 'top bottom',
end: 'bottom bottom',
scrub: 1,
duration: 1.5,
ease: 'power4.inOut',
markers: false,
},
});
});
});
}
////////////////////////////////////////////////////
// 50. zoom-in-full (badge-zoon-in)
if (typeof gsap !== 'undefined' && $('.badge-zoon-in').length > 0) {
gsap.to('.badge-zoon-in', {
scale: 25,
rotation: 180,
duration: 3,
scrollTrigger: {
trigger: '.badge-zoon-in',
start: 'top 60%',
end: 'bottom 0%',
scrub: 1,
markers: false,
},
});
gsap.to('.badge-text-zoom-in', {
scale: 8,
duration: 3,
scrollTrigger: {
trigger: '.badge-text-zoom-in',
start: 'top 70%',
end: 'bottom 0%',
scrub: 1,
markers: false,
},
});
}
///////////////////////////////////////////////////
////////////////////////////////////////////////////
// 51. scroll-section card stacking
if (typeof gsap !== 'undefined' && typeof ScrollTrigger !== 'undefined') {
function initScrollSectionStack() {
const scrollSection = document.querySelectorAll('.scroll-section');
scrollSection.forEach((section) => {
if (section.closest('.section-fix')) return;
const wrapper = section.querySelector('.wrapper');
const items = wrapper ? wrapper.querySelectorAll('.item') : [];
if (!items.length) return;
let direction = section.classList.contains('horizontal-section') ? 'horizontal' : 'vertical';
initScroll(section, items, direction);
});
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
initScrollSectionStack();
if (typeof ScrollTrigger !== 'undefined') ScrollTrigger.refresh();
});
} else {
initScrollSectionStack();
if (typeof ScrollTrigger !== 'undefined') ScrollTrigger.refresh();
}
}
function initScroll(section, items, direction) {
// Initial states + z-index so stacked absolute items show in correct order (current on top)
items.forEach((item, index) => {
gsap.set(item, { zIndex: index });
if (index !== 0) {
direction == 'horizontal' ? gsap.set(item, { xPercent: 100 }) : gsap.set(item, { yPercent: 100 });
}
});
const timeline = gsap.timeline({
scrollTrigger: {
trigger: section,
pin: true,
start: 'top top',
end: () => `+=${items.length * 50}%`,
scrub: 1,
invalidateOnRefresh: true,
// markers: true,
},
defaults: { ease: 'none' },
});
items.forEach((item, index) => {
timeline.to(item, {
scale: 0.9,
});
direction == 'horizontal'
? timeline.to(
items[index + 1],
{
xPercent: 0,
},
'<',
)
: timeline.to(
items[index + 1],
{
yPercent: 0,
},
'<',
);
});
}
////////////////////////////////////////////////////
// 52. change price / price plan toggle
function updatePriceByPlan(isBusiness) {
if (isBusiness) {
$('.text-price-starter').html('$2,400');
$('.text-price-growth').html('$5,600');
$('.text-price-scale').html('$7,200');
} else {
$('.text-price-starter').html('$1,200');
$('.text-price-growth').html('$2,800');
$('.text-price-scale').html('$3,600');
}
}
$('.change-price-plan .price-plan-toggle__input').on('change', function () {
updatePriceByPlan($(this).is(':checked'));
});
$('.change-price-plan .price-plan-toggle-label').on('click', function () {
var plan = $(this).attr('data-plan');
var $input = $('#price-plan-toggle');
if (plan === 'business') {
$input.prop('checked', true);
} else {
$input.prop('checked', false);
}
updatePriceByPlan($input.is(':checked'));
});
////////////////////////////////////////////////////
// 53. scroll-rotate, scroll-move-right/left
let scrollRotate = document.querySelectorAll('.scroll-rotate');
scrollRotate.forEach((scrollRotate) => {
gsap.to(scrollRotate, {
scrollTrigger: {
trigger: scrollRotate,
scrub: 2,
},
rotation: 720,
});
});
let scrollMoveRight = document.querySelectorAll('.scroll-move-right');
scrollMoveRight.forEach((scrollMoveRight) => {
gsap.to(scrollMoveRight, {
x: 500,
duration: 1.5,
scrollTrigger: {
trigger: scrollMoveRight,
start: 'top 30%',
scrub: 2,
},
});
});
let scrollMoveLeft = document.querySelectorAll('.scroll-move-left');
scrollMoveLeft.forEach((scrollMoveLeft) => {
gsap.to(scrollMoveLeft, {
x: -500,
duration: 1.5,
scrollTrigger: {
trigger: scrollMoveLeft,
start: 'bottom 100%',
scrub: 2,
// markers: true,
},
});
});
////////////////////////////////////////////////////
// 54. Journey list vertical line
if (typeof gsap !== 'undefined' && typeof ScrollTrigger !== 'undefined') {
const journeyListWrap = document.querySelector('.journey-list-wrap');
const journeyListLine = document.querySelector('.journey-list-line');
if (journeyListWrap && journeyListLine) {
gsap.set(journeyListLine, { height: 0 });
gsap.to(journeyListLine, {
height: '100%',
ease: 'none',
scrollTrigger: {
trigger: journeyListWrap,
start: 'top 50%',
end: 'top top',
scrub: 1.2,
invalidateOnRefresh: true,
// markers: true,
},
});
}
}
////////////////////////////////////////////////////
// 55. initPortfolioFilter
function initPortfolioFilter() {
const filterButtons = document.querySelectorAll('.filter-portfolio .filter-btn');
const portfolioCards = document.querySelectorAll('.mg-portfolio-item, .card-portfolio, .card_case__studies, .blog-card, .blog-card-2, .product-card');
if (filterButtons.length === 0 || portfolioCards.length === 0) {
return;
}
filterButtons.forEach(function (button) {
button.addEventListener('click', function (e) {
e.preventDefault();
const wasActive = this.classList.contains('active');
// Remove active class from all buttons
filterButtons.forEach(function (btn) {
btn.classList.remove('active');
});
// If clicking the already-active button, clear filter (show all); otherwise activate clicked button
if (!wasActive) {
this.classList.add('active');
}
// Use 'all' when clearing filter (click on active button), else use button's data-filter
const filterValue = wasActive ? 'all' : this.getAttribute('data-filter');
// Collect visible cards for stagger animation
const visibleCards = [];
portfolioCards.forEach(function (card) {
const cardCategory = card.getAttribute('data-category');
const isVisible = filterValue === 'all' || cardCategory === filterValue;
if (isVisible) {
visibleCards.push(card);
} else {
// Hide card with fade out animation
card.style.transition = 'opacity 0.3s ease, transform 0.3s ease';
card.style.opacity = '0';
card.style.transform = 'translateY(-5px)';
card.style.display = 'none';
card.style.transform = '';
}
});
// Show visible cards with slide-up + fade in, with stagger
var staggerDelayMs = 60;
var fadeDurationMs = 400;
visibleCards.forEach(function (card, index) {
card.style.display = '';
card.style.opacity = '0';
card.style.transform = 'translateY(24px)';
card.style.transition = 'opacity 0.4s ease, transform 0.4s ease';
var delay = index * staggerDelayMs;
setTimeout(function () {
card.style.opacity = '1';
card.style.transform = 'translateY(0)';
}, 10 + delay);
});
// After filtering, update the ScrollTrigger only when sidebar exists so the sidebar pins correctly to the new blog column height
var hasSidebar = document.querySelector('.sec-1-archive-2 .sidebar_right');
var lastStagger = visibleCards.length ? 10 + (visibleCards.length - 1) * staggerDelayMs : 0;
setTimeout(function () {
if (typeof ScrollTrigger !== 'undefined' && hasSidebar) {
ScrollTrigger.refresh();
}
}, lastStagger + fadeDurationMs);
});
});
}
////////////////////////////////////////////////////
// 56. initProductDetails
function initProductDetails() {
var right = document.querySelector('.content-product-right');
if (!right) return;
// Size selection
var sizeBtns = right.querySelectorAll('.content-product-right__size');
sizeBtns.forEach(function (btn) {
btn.addEventListener('click', function () {
sizeBtns.forEach(function (b) { b.classList.remove('active'); });
this.classList.add('active');
});
});
// Color selection
var colorBtns = right.querySelectorAll('.content-product-right__color');
colorBtns.forEach(function (btn) {
btn.addEventListener('click', function () {
colorBtns.forEach(function (b) { b.classList.remove('active'); });
this.classList.add('active');
});
});
// Quantity +/-
var qtyVal = right.querySelector('.content-product-right__qty-val, .qty-val');
var qtyDown = right.querySelector('.qty-down');
var qtyUp = right.querySelector('.qty-up');
if (qtyVal && qtyDown) {
qtyDown.addEventListener('click', function (e) {
e.preventDefault();
var n = parseInt(qtyVal.textContent, 10) || 1;
if (n > 1) qtyVal.textContent = n - 1;
});
}
if (qtyVal && qtyUp) {
qtyUp.addEventListener('click', function (e) {
e.preventDefault();
var n = parseInt(qtyVal.textContent, 10) || 0;
qtyVal.textContent = n + 1;
});
}
}
////////////////////////////////////////////////////
// 57. initShopCart (select all, quantity +/-, delete, summary)
function initShopCart() {
var $cartList = $('.cart-list');
if (!$cartList.length) return;
var $selectAll = $cartList.find('.cart-select-all');
var $itemChecks = $cartList.find('.cart-item-check');
var $removeSelected = $cartList.find('.cart-remove-selected');
var $summary = $('.cart-summary');
var appliedDiscount = 0; // amount in dollars, set when coupon applied
// Parse price string e.g. "$64" or "$72.50" to number
function parsePrice(str) {
if (!str || typeof str !== 'string') return 0;
var num = parseFloat(str.replace(/[$,]/g, '').trim(), 10);
return isNaN(num) ? 0 : num;
}
// Format number as currency
function formatMoney(num) {
return '$' + (Math.round(num * 100) / 100).toFixed(2);
}
// Get estimated delivery date (e.g. 5 business days)
function getEstimatedDeliveryDays() {
return 5;
}
function formatDeliveryDate(daysFromNow) {
var d = new Date();
d.setDate(d.getDate() + daysFromNow);
var months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
return d.getDate() + ' ' + months[d.getMonth()] + ' ' + d.getFullYear();
}
function updateCartSummary() {
if (!$summary.length) return;
var $items = $cartList.find('.cart-item');
var subtotal = 0;
$items.each(function () {
var $item = $(this);
var price = parsePrice($item.find('.cart-item__price').text());
var qty = parseInt($item.find('.cart-item-qty-val').text(), 10) || 1;
subtotal += price * qty;
});
var discount = Math.min(appliedDiscount, subtotal);
var tax = 0;
var subtotalAfter = Math.max(0, subtotal - discount);
var shipping = 0; // Free
var total = subtotalAfter + shipping;
$summary.find('.cart-summary__subtotal').text(formatMoney(subtotal));
$summary.find('.cart-summary__discount').text(formatMoney(discount));
$summary.find('.cart-summary__tax').text(formatMoney(tax));
$summary.find('.cart-summary__subtotal-after').text(formatMoney(subtotalAfter));
$summary.find('.cart-summary__shipping').text('Free');
$summary.find('.cart-summary__total').text(formatMoney(total));
$summary.find('.cart-summary__delivery-date').text(formatDeliveryDate(getEstimatedDeliveryDays()));
}
// Coupon apply: optional demo – set discount and update summary
$summary.on('click', '.cart-summary__coupon-btn', function (e) {
e.preventDefault();
var code = $summary.find('.cart-summary__coupon-input').val().trim().toUpperCase();
// Demo: e.g. "SAVE10" = $10 off, "SAVE20" = $20 off
if (code === 'SAVE10') appliedDiscount = 10;
else if (code === 'SAVE20') appliedDiscount = 20;
else appliedDiscount = 0;
updateCartSummary();
});
// Select All: check/uncheck all item checkboxes
$selectAll.on('change', function () {
var checked = this.checked;
$itemChecks.prop('checked', checked);
});
// Item checkbox: sync "Select All" state
$itemChecks.on('change', function () {
var total = $itemChecks.length;
var checked = $itemChecks.filter(':checked').length;
$selectAll.prop('checked', total > 0 && total === checked);
});
// Remove selected: remove all checked items from DOM
$removeSelected.on('click', function () {
$cartList.find('.cart-item-check:checked').closest('.cart-item').fadeOut(300, function () {
$(this).remove();
updateCartSummary();
});
});
// Quantity minus
$cartList.on('click', '.cart-item-qty-minus', function (e) {
e.preventDefault();
var $val = $(this).siblings('.cart-item-qty-val');
var n = parseInt($val.text(), 10) || 1;
if (n > 1) {
$val.text(n - 1);
updateCartSummary();
}
});
// Quantity plus
$cartList.on('click', '.cart-item-qty-plus', function (e) {
e.preventDefault();
var $val = $(this).siblings('.cart-item-qty-val');
var n = parseInt($val.text(), 10) || 0;
$val.text(n + 1);
updateCartSummary();
});
// Delete single item
$cartList.on('click', '.cart-item-delete', function (e) {
e.preventDefault();
$(this).closest('.cart-item').fadeOut(300, function () {
$(this).remove();
updateCartSummary();
});
});
// Initial summary and delivery date
updateCartSummary();
}
initPortfolioFilter();
initProductDetails();
initShopCart();
////////////////////////////////////////////////////
// 58. text-scramble
function initTextScramble() {
const targets = document.querySelectorAll('.text-scramble');
if (!targets.length) return;
const CHARS = '!<>-_\\/[]{}—=+*^?#________ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
const pickChar = () => CHARS[Math.floor(Math.random() * CHARS.length)];
function scramble(el, finalText, duration = 1.2) {
if (el._scrambling) return;
el._scrambling = true;
// Work from a text-only copy so HTML entities (e.g. °) stay intact.
const original = finalText;
const length = original.length;
// Build per-char queue: when to start revealing, when to end scrambling.
const queue = [];
for (let i = 0; i < length; i++) {
const start = Math.floor(Math.random() * (duration * 0.4) * 60); // frames
const end = start + Math.floor(Math.random() * (duration * 0.6) * 60) + 6;
queue.push({ from: original[i], to: original[i], start, end, char: '' });
}
let frame = 0;
const update = () => {
let output = '';
let complete = 0;
for (let i = 0; i < queue.length; i++) {
const q = queue[i];
if (frame >= q.end) {
complete++;
output += q.to;
} else if (frame >= q.start) {
// Space & punctuation stay fixed so layout is stable.
if (/\s/.test(q.to) || q.to === ',' || q.to === '.') {
output += q.to;
} else {
if (!q.char || Math.random() < 0.28) {
q.char = pickChar();
}
output += q.char;
}
} else {
output += q.from;
}
}
el.textContent = output;
if (complete === queue.length) {
gsap.ticker.remove(update);
el._scrambling = false;
} else {
frame++;
}
};
gsap.ticker.add(update);
}
targets.forEach((el) => {
const finalText = el.dataset.scrambleText || el.textContent;
el.dataset.scrambleText = finalText;
// Trigger once on scroll into view.
if (typeof ScrollTrigger !== 'undefined') {
ScrollTrigger.create({
trigger: el,
start: 'top 90%',
once: true,
onEnter: () => scramble(el, finalText, 1.2),
});
} else {
scramble(el, finalText, 1.2);
}
// Replay on hover for fun.
el.addEventListener('mouseenter', () => scramble(el, finalText, 0.9));
});
}
if (typeof gsap !== 'undefined') {
initTextScramble();
} else {
$(window).on('load', initTextScramble);
}
////////////////////////////////////////////////////
// hover parallax
// Containers with class `.at-hover-parallax` drive the first `img`
// (or `[data-parallax-target]`) inside them in the opposite direction
// of the cursor, creating a depth illusion.
//
// Optional data attributes on the container:
// data-parallax-strength Max translation in px (default 15)
// data-parallax-scale Scale-up factor while hovering (default 1.04)
function initHoverParallax() {
const containers = document.querySelectorAll('.at-hover-parallax');
if (!containers.length || typeof gsap === 'undefined') return;
containers.forEach((wrap) => {
if (window.innerWidth < 992) return;
const target =
wrap.querySelector('[data-parallax-target]') ||
wrap.querySelector('img') ||
wrap.firstElementChild;
if (!target) return;
const strength = parseFloat(wrap.getAttribute('data-parallax-strength')) || 15;
const scaleTo = parseFloat(wrap.getAttribute('data-parallax-scale')) || 1.04;
const xTo = gsap.quickTo(target, 'x', { duration: 0.55, ease: 'power2.out' });
const yTo = gsap.quickTo(target, 'y', { duration: 0.55, ease: 'power2.out' });
wrap.addEventListener('mouseenter', () => {
gsap.to(target, { scale: scaleTo, duration: 0.4, ease: 'power2.out' });
});
wrap.addEventListener('mousemove', (e) => {
const rect = wrap.getBoundingClientRect();
// Normalise cursor position to [-1, +1] relative to container centre
const nx = (e.clientX - rect.left - rect.width / 2) / (rect.width / 2);
const ny = (e.clientY - rect.top - rect.height / 2) / (rect.height / 2);
xTo(nx * strength);
yTo(ny * strength);
});
wrap.addEventListener('mouseleave', () => {
xTo(0);
yTo(0);
gsap.to(target, { scale: 1, duration: 0.55, ease: 'power2.out' });
});
});
}
if (typeof gsap !== 'undefined') {
initHoverParallax();
} else {
$(window).on('load', initHoverParallax);
}
////////////////////////////////////////////////////
// scroll-driven continuous rotation
// Elements with class `.at-scroll-rotate` rotate continuously at a
// base speed. Scrolling boosts the rotation speed, and the direction
// of rotation follows the scroll direction. When the user stops
// scrolling, the element eases back to its idle rotation.
//
// Optional data attributes:
// data-rotate-duration Seconds per full rotation at idle (default 20)
// data-rotate-sensitivity How strongly scroll speed boosts rotation (default 0.18)
// data-rotate-boost Max additional timeScale from scroll (default 12)
function initScrollRotate() {
const items = document.querySelectorAll('.at-scroll-rotate');
if (!items.length || typeof gsap === 'undefined') return;
items.forEach((el) => {
const baseDuration = parseFloat(el.getAttribute('data-rotate-duration')) || 20;
const sensitivity = parseFloat(el.getAttribute('data-rotate-sensitivity')) || 0.18;
const maxBoost = parseFloat(el.getAttribute('data-rotate-boost')) || 12;
gsap.set(el, { transformOrigin: '50% 50%' });
const rotateTween = gsap.to(el, {
rotation: '+=360',
duration: baseDuration,
ease: 'none',
repeat: -1,
});
let lastScroll = window.scrollY || window.pageYOffset || 0;
let targetTimeScale = 1;
let currentTimeScale = 1;
let decayTimer = null;
const onScroll = () => {
const now = window.scrollY || window.pageYOffset || 0;
const delta = now - lastScroll;
lastScroll = now;
if (delta === 0) return;
const boost = Math.min(Math.abs(delta) * sensitivity, maxBoost);
targetTimeScale = delta > 0 ? 1 + boost : -(1 + boost);
if (decayTimer) clearTimeout(decayTimer);
decayTimer = setTimeout(() => {
targetTimeScale = 1;
}, 140);
};
window.addEventListener('scroll', onScroll, { passive: true });
gsap.ticker.add(() => {
currentTimeScale += (targetTimeScale - currentTimeScale) * 0.12;
rotateTween.timeScale(currentTimeScale);
});
});
}
if (typeof gsap !== 'undefined') {
initScrollRotate();
} else {
$(window).on('load', initScrollRotate);
}
})(jQuery);